package com.hunray.rent.dao;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

public class BaseDao<T, I extends Serializable> extends
		SimpleJpaRepository<T, I> implements IBaseDao<T, I> {

	private final EntityManager em;

	public BaseDao(Class<T> domainClass, EntityManager em) {
		super(domainClass, em);
		this.em = em;
	}

	public BaseDao(JpaEntityInformation<T, ?> entityInformation,
			EntityManager entityManager) {
		super(entityInformation, entityManager);
		this.em = entityManager;
	}

	private void setParams(Query query, Object[] args) {
		if (args != null && args.length > 0) {
			int index = 1;
			for (Object arg : args) {
				query.setParameter(index++, arg);
			}
		}
	}

	@SuppressWarnings("rawtypes")
	private void setParams(Query query, Map<String, Object> params) {
		if (params != null && !params.isEmpty()) {
			Set<String> keys = params.keySet();
			for (String key : keys) {
				Object value = params.get(key);
				if (value instanceof Collection) {
					query.setParameter(key, (Collection) value);
				} else {
					query.setParameter(key, value);
				}
			}
		}

	}

	private void setParams(Query query, Object[] args,
			Map<String, Object> params) {
		setParams(query, args);
		setParams(query, params);
	}

	private String initSortString(String jpql, Map<String, String> orders) {
		if (orders != null && !orders.isEmpty()) {
			jpql += " order by ";
			Set<String> keys = orders.keySet();
			for (String key : keys) {
				jpql += "bean." + key + " " + orders.get(key).toUpperCase()
						+ ",";
			}
			jpql = jpql.substring(0, jpql.length() - 1);
		}
		return jpql;
	}

	private String getConutJpal(String jpql) {
		// return "select count(*) " + jpql.substring(jpql.indexOf("from"));
		if (jpql.toLowerCase().contains("order by")) {
			jpql = jpql
					.substring(0, jpql.toLowerCase().indexOf("order by") - 1);
		}
		if (jpql.toLowerCase().contains("group by")) {
			jpql = jpql
					.substring(0, jpql.toLowerCase().indexOf("group by") - 1);
		}
		jpql = "select count(*) " + jpql.substring(jpql.indexOf("from"));
		return jpql;
	}

	private Query buildJpqlQuery(String queryString, Object[] args,
			Map<String, Object> params, Map<String, String> orders) {
		queryString = initSortString(queryString, orders);
		Query query = em.createQuery(queryString);
		setParams(query, args, params);
		return query;
	}


	private Query buildSqlQuery(String queryString, Object[] args,
			Map<String, Object> params, Map<String, String> orders) {
		queryString = initSortString(queryString, orders);
		Query query = em.createNativeQuery(queryString, this.getDomainClass());
		setParams(query, args, params);
		return query;
	}

	// jqpl可以采用支持别名的如下写法： select bean from User bean where bean.id=?1
	@SuppressWarnings("unchecked")
	@Override
	public List<T> queryByJpql(String jpql, Object[] args,
			Map<String, Object> params, Map<String, String> orders) {
		Query query = buildJpqlQuery(jpql, args, params, orders);
		return query.getResultList();
	}

	// sql必须采用如下写法： select * from t_user as bean where bean.id=?1
	@SuppressWarnings("unchecked")
	@Override
	public List<T> queryBySql(String sql, Object[] args,
			Map<String, Object> params, Map<String, String> orders) {
		Query query = buildSqlQuery(sql, args, params, orders);
		return (List<T>) query.getResultList();
	}
	
	
	@SuppressWarnings("unchecked")
	@Override
	public Page<T> queryByJpql(String jpql, Object[] args,
			Map<String, Object> params, Map<String, String> orders,
			Pageable pageable) {
		Query query = buildJpqlQuery(jpql, args, params, orders);
		query.setFirstResult(pageable.getOffset()).setMaxResults(
				pageable.getPageSize());
		List<T> elements = query.getResultList();
		String totalJpql = getConutJpal(jpql);
		Query totalQuery = buildJpqlQuery(totalJpql, args, params, orders);
		long total = (long) totalQuery.getSingleResult();
		return new PageImpl<T>(elements, pageable, total);
	}

	@SuppressWarnings("unchecked")
	@Override
	public Page<T> queryBySql(String sql, Object[] args,
			Map<String, Object> params, Map<String, String> orders,
			Pageable pageable) {
		Query query = buildSqlQuery(sql, args, params, orders);
		query.setFirstResult(pageable.getOffset()).setMaxResults(
				pageable.getPageSize());
		List<T> elements = query.getResultList();
		String totalSql = getConutJpal(sql);
		Query totalQuery = em.createNativeQuery(totalSql);
		setParams(totalQuery, args, params);
		long total = ((BigInteger) totalQuery.getSingleResult()).longValue();
		return new PageImpl<T>(elements, pageable, total);
	}

	@Override
	public void updateOrDeleteByJpql(String jpql, Object[] args,
			Map<String, Object> params) {
		Query query = em.createQuery(jpql);
		setParams(query, args);
		setParams(query, params);
		query.executeUpdate();
	}

}
